<template>
	<div id="global-uploader" :class="{ 'global-uploader-single': !global }">
		<!-- 上传 -->
		<Uploader
			ref="uploaderRef"
			class="uploader-app"
			:options="initOptions"
			:file-status-text="fileStatusText"
			:auto-start="false"
			@file-added="onFileAdded"
			@file-success="onFileSuccess"
			@file-progress="onFileProgress"
			@file-error="onFileError"
		>
			<uploader-unsupport></uploader-unsupport>

			<uploader-btn id="global-uploader-btn" ref="uploadBtnRef">选择文件</uploader-btn>

			<uploader-list v-show="panelShow" ref="uploadListRef">
				<template #default="{ fileList }">
					<div class="file-panel" :class="{ collapse: collapse }">
						<div class="file-title">
							<div class="title">文件列表</div>
							<div class="operate">
								<el-button :title="collapse ? '展开' : '折叠'" link @click="collapse = !collapse">
									<Icon :icon="collapse ? 'ri:fullscreen-fill' : 'ri:subtract-fill'" width="18" />
								</el-button>
								<el-button title="关闭" link @click="close">
									<Icon icon="ri:close-fill" width="20" />
								</el-button>
							</div>
						</div>
						<ul class="file-list">
							<li v-for="file in fileList" :key="file.id" class="file-item">
								<uploader-file ref="files" :class="['file_' + file.id]" :file="file" :list="true"></uploader-file>
							</li>
							<div v-if="!fileList.length" class="no-file"><Icon icon="ri:file-3-line" width="16" /> 暂无待上传文件</div>
						</ul>
					</div>
				</template>
			</uploader-list>
		</Uploader>
	</div>
</template>

<script setup lang="ts">
import { ref, watch, computed, nextTick, onMounted, defineAsyncComponent } from 'vue';
import { generateMD5 } from '/@/components/uploader/utils/md5';
import mittBus from '/@/utils/mitt';
import { UPLOAD_URL } from '/@/components/uploader/utils/uploader';
import { upload } from '/@/api/file/upload/index';
import msg from '/@/utils/messageUtils';

const Uploader = defineAsyncComponent(() => import('/@/components/uploader/vue-simple-uploader/uploader.vue'));

const UploaderUnsupport = defineAsyncComponent(() => import('/@/components/uploader/vue-simple-uploader/unsupport.vue'));
const UploaderBtn = defineAsyncComponent(() => import('/@/components/uploader/vue-simple-uploader/btn.vue'));
const UploaderList = defineAsyncComponent(() => import('/@/components/uploader/vue-simple-uploader/list.vue'));
const UploaderFile = defineAsyncComponent(() => import('/@/components/uploader/vue-simple-uploader/file.vue'));

const uploadListRef = ref();

const props = defineProps({
	global: {
		type: Boolean,
		default: true,
	},
	// 发送给服务器的额外参数
	params: {
		type: Object,
	},
	options: {
		type: Object,
	},
});

const emits = defineEmits(['fileAdded', 'fileSuccess', 'saveFile']);
const initOptions = {
	target: UPLOAD_URL,
	chunkSize: '2048000',
	fileParameterName: 'file',
	maxChunkRetries: 3,
	// 是否开启服务器分片校验
	testChunks: true,
	// 服务器分片校验函数，秒传及断点续传基础
	checkChunkUploadedByResponse: function (chunk, message) {
		let skip = false;
		// console.log('chunk: ', chunk);
		try {
			let objMessage = JSON.parse(message);
			if (objMessage.skipUpload) {
				skip = true;
			} else {
				skip = (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0;
			}
		} catch (e) {}

		return false;
	},
	query: (file, chunk) => {
		return {
			...file.params,
		};
	},
};

const fileStatusText = {
	success: '上传成功',
	error: '上传失败',
	uploading: '上传中',
	paused: '已暂停',
	waiting: '等待上传',
};
const panelShow = ref(false);
const collapse = ref(false);
const uploaderRef = ref();
const uploadBtnRef = ref();

const uploader = computed(() => uploaderRef.value?.uploader);

let customParams = {};

watch(
	() => props.params,
	(data) => {
		if (data) {
			customParams = data;
		}
	}
);
watch(
	() => props.options,
	(data) => {
		if (data) {
			setTimeout(() => {
				customizeOptions(data);
			}, 0);
		}
	}
);

const mergeSimpleUpload = () => {
	return Promise.resolve();
};

function customizeOptions(opts) {
	// 自定义上传url
	if (opts.target) {
		uploader.value.opts.target = opts.target;
	}
	// 是否可以秒传、断点续传
	if (opts.testChunks !== undefined) {
		uploader.value.opts.testChunks = opts.testChunks;
	}
	// merge 的方法，类型为Function，返回Promise
	if (opts.mergeFn) {
		// mergeFn = opts.mergeFn;
	}
	// 自定义文件上传类型
	if (opts.accept) {
		let input = document.querySelector('#global-uploader-btn input');
		input.setAttribute(opts.accept, accept.join());
	}
}

async function onFileAdded(file) {
	panelShow.value = true;
	trigger('fileAdded');
	// 将额外的参数赋值到每个文件上，以不同文件使用不同params的需求
	file.params = customParams.value;
	const md5 = await computeMD5(file);
	startUpload(file, md5);
}

const statusMap = {
	md5: {
		text: '校验MD5',
		bgc: '#fff',
	},
	merging: {
		text: '合并中',
		bgc: '#e2eeff',
	},
	transcoding: {
		text: '转码中',
		bgc: '#e2eeff',
	},
	failed: {
		text: '上传失败',
		bgc: '#e2eeff',
	},
};

function computeMD5(file) {
	// 文件状态设为"计算MD5"
	statusSet(file, 'md5');
	// 暂停文件
	file.pause();
	// 计算MD5时隐藏”开始“按钮

	// 开始计算MD5
	return new Promise((resolve, reject) => {
		generateMD5(file, {
			onProgress(currentChunk, chunks) {
				const md5ProgressText = '校验MD5 ' + ((currentChunk / chunks) * 100).toFixed(0) + '%';
				file.customStatusText = md5ProgressText;
			},
			onSuccess(md5) {
				statusRemove(file);
				resolve(md5);
			},
			onError() {
				error(`文件${file.name}读取出错，请检查该文件`);
				file.cancel();
				statusRemove(file);
				reject();
			},
		});
	});
}
// md5计算完毕，开始上传
function startUpload(file, md5) {
	file.uniqueIdentifier = md5;
	file.resume();
}
async function onFileSuccess(rootFile, file, response, chunk) {
	let res = JSON.parse(response);
	// 如果服务端返回了需要合并的参数
	if (res.data === 'merge') {
		// 文件状态设为“合并中”
		statusSet(file, 'merging');
		console.log('file: ', file);
		file.customStatusText = '合并中';
		try {
			const { data } = await upload().merge({ identifier: file.uniqueIdentifier, filename: file.name, fileType: file.fileType });
			statusRemove(file);
			const uf: UploadFile = {
				identifier: file.uniqueIdentifier,
				filename: file.name,
				fileType: file.fileType,
				relativePath: data,
			};

			emits('saveFile', uf);
		} catch (err) {
			//
			statusSet(file, 'failed');
			console.log('合并文件错误');
		}
		// todo 这里发送合并请求
	} else {
		trigger('fileSuccess');
		console.log('上传成功');
	}
}
function onFileProgress(rootFile, file, chunk) {}
function onFileError(rootFile, file, response, chunk) {
	error(response);
}
function close() {
	uploader.value.cancel();
	panelShow.value = false;
}
/**
 * 新增的自定义的状态: 'md5'、'merging'、'transcoding'、'failed'
 * @param id
 * @param status
 */
function statusSet(file, status) {
	file.customStatus = status;
	file.customStatusText = statusMap[status].text;
}
function statusRemove(file) {
	file.customStatus = '';
}
function trigger(e) {
	emits(e);
}
function error(msgs: any) {
	msg().error(msgs);
}

onMounted(() => {
	mittBus.on('openUploader', ({ params = {}, options = {} }) => {
		customParams = params;
		if (uploadBtnRef.value) {
			uploadBtnRef.value.$el.click();
		}
	});
});

const uploadListDialog = () => {
	console.log('文件提交完毕');
	// uploaderRef.value.clearUploadFileList();
	panelShow.value = false;
	uploadListRef.value.clearFileList();
};

defineExpose({
	uploadListDialog,
});
</script>
<style scoped lang="scss">
#global-uploader {
	&:not(.global-uploader-single) {
		position: fixed;
		z-index: 20;
		right: 15px;
		bottom: 15px;
		box-sizing: border-box;
	}

	.uploader-app {
		width: 520px;
	}

	.file-panel {
		background-color: #fff;
		border: 1px solid #e2e2e2;
		border-radius: 7px 7px 0 0;
		box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);

		.file-title {
			display: flex;
			height: 40px;
			line-height: 40px;
			padding: 0 15px;
			border-bottom: 1px solid #ddd;

			.operate {
				flex: 1;
				text-align: right;

				.el-button {
					+ .el-button {
						margin-left: 8px;
					}
				}
			}
		}

		.file-list {
			position: relative;
			height: 240px;
			overflow-x: hidden;
			overflow-y: auto;
			background-color: #fff;
			transition: all 0.3s;

			.file-item {
				background-color: #fff;
			}
		}

		&.collapse {
			.file-title {
				background-color: #e7ecf2;
			}
			.file-list {
				height: 0;
			}
		}
	}

	.no-file {
		position: absolute;
		top: 45%;
		left: 50%;
		transform: translate(-50%, -50%);
		color: #999;

		svg {
			vertical-align: text-bottom;
		}
	}

	.uploader-file {
		&.md5 {
			.uploader-file-resume {
				display: none;
			}
		}
	}

	.uploader-file-icon {
		&:before {
			content: '' !important;
		}

		&[icon='image'] {
			background: url(./images/image-icon.png);
		}
		&[icon='audio'] {
			background: url(./images/audio-icon.png);
			background-size: contain;
		}
		&[icon='video'] {
			background: url(./images/video-icon.png);
		}
		&[icon='document'] {
			background: url(./images/text-icon.png);
		}
		&[icon='unknown'] {
			background: url(./images/zip.png) no-repeat center;
			background-size: contain;
		}
	}

	.uploader-file-actions > span {
		margin-right: 6px;
	}

	.custom-status {
		position: absolute;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		z-index: 1;
	}
}

/* 隐藏上传按钮 */
#global-uploader-btn {
	position: absolute;
	clip: rect(0, 0, 0, 0);
}

.global-uploader-single {
	#global-uploader-btn {
		position: relative;
	}
}
</style>
